/*
    This file is part of Posixcpp library.

    Posixcpp library is free software: you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public License
    as published by the Free Software Foundation, either version 3 of
    the License, or (at your option) any later version.

    Posixcpp library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU LesserGeneral Public
    License along with Posixcpp library. If not, see
    <http://www.gnu.org/licenses/>.
*/

/**
 * @file posix_mutex.cpp
 * @brief Mutex class implementation
 */

#include "posix_threads_mutex.hpp"
#include "posixcpp.hpp"

using posix::threads::Mutex;
using namespace posix::threads;

Mutex::Mutex()
{
	POSIX_CALL_ASSERT_X((errno = pthread_mutex_init(&m_handle, NULL)) == 0,
					"Unable to initialize mutex");
}

Mutex::Mutex(const Attributes & attributes)
{
	POSIX_CALL_ASSERT_X((errno = pthread_mutex_init(&m_handle,
												&attributes.handle())) == 0,
					"Unable to initialize mutex");
}

Mutex::~Mutex()
{
	POSIX_CALL_ASSERT_X((errno = pthread_mutex_destroy(&m_handle)) == 0,
					"Unable to destroy mutex");
}

void Mutex::lock()
{
	POSIX_CALL_ASSERT_X((errno = pthread_mutex_lock(&m_handle)) == 0,
					"Unable to lock mutex");
}

bool Mutex::lock(posix::time::Timespec timeout)
{
	int res = pthread_mutex_timedlock(&m_handle, &timeout.data());
	switch (res)
	{
	case 0:
		return true;
	case ETIMEDOUT:
		return false;
	default:
		POSIX_CALL_ASSERT_X( res == 0,
						"Unable to process timed lock of the mutex");
		return false;
	}
}

bool Mutex::tryLock()
{
	int res = pthread_mutex_trylock(&m_handle);
	switch(res)
	{
	case 0:
		return true;
	case EBUSY:
		return false;
	default:
		POSIX_CALL_ASSERT_X( res == 0,
						 "Unable to try lock the mutex");
		return false;
	}
}

void Mutex::unlock()
{
	POSIX_CALL_ASSERT_X((errno = pthread_mutex_unlock(&m_handle)) == 0,
					"Unable to unlock mutex");
}

Mutex::Attributes::Attributes()
{
	POSIX_CALL_ASSERT_X((errno = pthread_mutexattr_init(&m_handle)) == 0,
					"Unable to initialize mutex attributes");
}

Mutex::Attributes::Attributes( Type_t type, int priorityCeiling )
{
	POSIX_CALL_ASSERT_X((errno = pthread_mutexattr_init(&m_handle)) == 0,
					"Unable to initialize mutex attributes");
	POSIX_CALL_ASSERT_X((errno = pthread_mutexattr_setprioceiling( &m_handle,
                                                                  priorityCeiling)) == 0,
            "Unable to set mutex priority ceiling in attributes");
	POSIX_CALL_ASSERT_X((errno = pthread_mutexattr_settype(&m_handle,
														int(type))) == 0,
					"Unable to set mutex type in attributes");
}

Mutex::Attributes::~Attributes()
{
	POSIX_CALL_ASSERT_X((errno = pthread_mutexattr_destroy(&m_handle)) == 0,
					"Unable to destroy mutexattributes");
}

void Mutex::Attributes::setType(posix::threads::Mutex::Type_t newVal)
{
	POSIX_CALL_ASSERT_X((errno = pthread_mutexattr_settype(&m_handle,
														int(newVal))) == 0,
					"Unable to set mutex type in attributes");
}

posix::threads::Mutex::Type_t Mutex::Attributes::type()
{
    int result;
    POSIX_CALL_ASSERT_X((errno = pthread_mutexattr_gettype(&m_handle,
														&result)) == 0,
					"Unable to get mutex type in attributes");
	return Type_t(result);
}

void Mutex::Attributes::setPriorityCeiling( int newVal )
{
    POSIX_CALL_ASSERT_X((errno = pthread_mutexattr_setprioceiling( &m_handle,
                                                                  newVal)) == 0,
            "Unable to set mutex priority ceiling in attributes");
}

int Mutex::Attributes::getPriorityCeiling( )
{
	int result;
    POSIX_CALL_ASSERT_X((errno = pthread_mutexattr_getprioceiling( &m_handle,
                                                                  &result)) == 0,
					"Unable to get mutex priority ceiling in attributes");
	return result;
}

namespace posix
{
namespace threads
{
std::ostream & operator <<(	std::ostream & stream,
							posix::threads::Mutex::Type_t data)
{
	switch (data)
	{
	case Mutex::Type_t::NORMAL: stream << "NORMAL";
		break;
	case Mutex::Type_t::RECURSIVE: stream << "RECURSIVE";
		break;
	case Mutex::Type_t::SAFE: stream << "SAFE";
		break;
	default: stream << "(unknown)";
	}

	return stream;
}
}
}
